'/
' Fetherquest Online
' 
' =========================================================================
' 
' This program is free software: you can redistribute it and/or modify
' it under the terms of the GNU General Public License as published by
' the Free Software Foundation, either version 3 of the License, or
' (at your option) any later version.
' 
' This program is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY; without even the implied warranty of
' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
' GNU General Public License for more details.
' 
' You should have received a copy of the GNU General Public License
' along with this program.  If not, see <http://www.gnu.org/licenses/>.
'/

include "Command.bmx"

type ConsoleManager extends Base

	'// -------------------------------------------------------------------------------------------------------------
	'// Size of each line in the console.
	'// -------------------------------------------------------------------------------------------------------------
	global LINE_SIZE:short = 12;
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Maximum amount of characters of the command line that are shown.
	'// -------------------------------------------------------------------------------------------------------------
	global MAX_LINE_LENGTH:short = 52;
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Standard config file.
	'// -------------------------------------------------------------------------------------------------------------
	global CONFIG_FILE:string = "Data/config.cfg";
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Initialize the console manager.
	'// -------------------------------------------------------------------------------------------------------------
	method init()
	
		'// Load the standard config.
		writeLog("Initializing console.");
		execConfig(CONFIG_FILE);
		
		addCommand("_shutdown", CommandManager.shutdown_client);
		
		'// Create commands that affect the renderer.
		addCommand("_r_fullscreenmode", CommandManager.set_fullscreen_mode);
		addCommand("_r_windowmode", CommandManager.set_window_mode);
		addCommand("_vid_restart", CommandManager.reset_video);
		
		'// Create commands that affect the client interface.
		addCommand("_cg_drawFps", CommandManager.toggle_fps_text);
	
	end method
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Update the console.
	'// -------------------------------------------------------------------------------------------------------------
	method update()
	
		'// If the console is not visible, don't try to draw it.
		if (not isVisible()) then
		
			if (GUI.m_deactive) ..
				GUI.activate();
			if (not sInputManager.m_deactive) ..
				sInputManager.deactivate();
			
			return;
		
		end if
		
		if (not GUI.m_deactive) ..
			GUI.deactivate();
		if (sInputManager.m_deactive) ..
			sInputManager.activate();
		
		'// If the console has not reached half way down the screen, slide it down.
		if (m_posY < sVideo.RESOLUTION_Y shr 1) then
		
			m_posY :+ m_step;
		
		else
		
			'// If it's already down, hide it again, if 'm_step' is a negative number.
			if (sgn m_step = -1) ..
				m_posY :+ m_step;
			
			'// Make sure that we don't move the crosshair while the console is shown.
			moveMouse(sVideo.RESOLUTION_X shr 1, sVideo.RESOLUTION_Y shr 1);
		
		end if
	
	end method
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Handle the input from the keyboard. This method is called from 'sInputManager'.
	'// -------------------------------------------------------------------------------------------------------------
	method handleInput(currentKeyStroke:int)
	
		'// Writable key?
		local key:int = keycodes.isWritableKey(currentKeyStroke);
		if (key) ..
			m_input :+ chr(key);
		
		'// Functional key?
		local funcKey:int = currentKeyStroke;
		if (funcKey = KEY_SPACE) then
		
			m_input :+ " ";
		
		end if
		if (funcKey = KEY_BACKSPACE) then
		
			m_input = m_input[..m_input.length - 1];
		
		end if
		if (funcKey = KEY_ENTER) then
		
			execCommand(m_input);
			m_input = null;
		
		end if
	
	end method
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Execute a command line.
	'// -------------------------------------------------------------------------------------------------------------
	method execCommand(cmd:string, echo:byte = true)
	
		if (not cmd) ..
			return;
		
		if (echo) ..
			writeLog("]" + cmd);
		
		local cmdName:string = strutil.word(0, cmd).toLower();
		local cmdArgsList:string[] = strutil.wssplit(cmd);
		local cmdArgs:string;
		
		for local arg:string = eachin cmdArgsList
		
			if (arg = cmdName) ..
				continue;
			
			if (cmdArgs) ..
				cmdArgs = " " + arg; ..
			else ..
				cmdArgs = arg;
		
		next
		
		if (m_cmdMap.valueForKey(cmdName)) then
			
			local cmd:Command = Command(m_cmdMap.valueForKey(cmdName));
			cmd.invoke(cmdArgs);
			
			return;
		
		end if
		
		select (cmdName)
			
			case "_clear"
				m_log.clear();
				return;
		
		end select
		
		writeLog("Unknown command or cvar."); ..
	
	end method
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Load a configuration file to memory and execute it.
	'// -------------------------------------------------------------------------------------------------------------
	method execConfig(url:string)
	
		writeLog("Executing config: " + url + ".");
	
	end method
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Draw the console.
	'// -------------------------------------------------------------------------------------------------------------
	method drawConsole()
	
		'// Draw the background.
		drawutil.setAlphaInit(0.75);
		drawutil.setColorInit(24, 24, 24);
		drawRect(0, 0, sVideo.RESOLUTION_X, m_posY);
		setAlpha(1.0);
		
		'// Draw the log.
		local offset:int = 4 + m_posY - sVideo.RESOLUTION_Y shr 1;
		local i:int;
		
		setColor(100, 60, 200);
		
		for local str:string = eachin m_log
		
			local y:int = offset + i * LINE_SIZE;
			if (y > 0) then
			
				local charSize:int = sVideo.RESOLUTION_X / textWidth(str[..1]);
				local strOffset:int = str.length - charSize;
				
				if (strOffset < 0) ..
					strOffset = 0;
				
				drawText(str[strOffset..str.length], 8, y);
			
			end if
			i :+ 1;
		
		next
		
		'// Draw the input.
		local inputOffset:int = m_input.length - MAX_LINE_LENGTH;
		local yOffset:int = offset + (sVideo.RESOLUTION_Y shr 1 / LINE_SIZE - 2) * LINE_SIZE;
		
		if (inputOffset < 0) ..
			inputOffset = 0;
		
		drawText(m_input[inputOffset..m_input.length], 8, yOffset);
		
		'// Draw the position marker.
		local charWidth:int = textWidth(m_input[..1]);
		local linelen:int = textWidth(m_input);
		if (linelen > MAX_LINE_LENGTH*charWidth) ..
			linelen = MAX_LINE_LENGTH*charWidth;
		local xStart:int = 8 + linelen;
		local xEnd:int = xStart + charWidth;
		drawLine(xStart, yOffset + LINE_SIZE, xEnd, yOffset + LINE_SIZE);
		
		'// Draw the game name and version.
		local str:string = sMaster.GAME_NAME + " " + sMaster.GAME_VERSION;
		drawText(str, sVideo.RESOLUTION_X - textWidth(str) - 8, yOffset);
		
		'// Reset the drawing settings.
		drawutil.resetColor();
		drawutil.resetAlpha();
	
	end method
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Dump the log to a file.
	'// -------------------------------------------------------------------------------------------------------------
	method dumpLogToFile(url:string);
	
		if (fileType(url)) ..
			deleteFile(url);
		
		if (not createFile(url)) ..
			return;
		
		local file:TStream = writeStream(url);
		
		for local str:string = eachin m_log
		
			file.writeLine(str);
		
		next
		
		closeStream(file);
	
	end method
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Write a line of text to the log.
	'// -------------------------------------------------------------------------------------------------------------
	method writeLog(str:string)
	
		if (sVideo.RESOLUTION_Y) ..
			if (CountList(m_log) >= (sVideo.RESOLUTION_Y shr 1 / LINE_SIZE - 2)) ..
				m_log.firstLink().remove();
		
		m_log.addLast(str);
	
	end method
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Print a fatal error message to the console log and exit the application.
	'// -------------------------------------------------------------------------------------------------------------
	method fatalerror(str:string)
	
		writeLog("Fatal error: " + str);
		if (not isVisible()) ..
			toggle();
		delay(1000);
		dumpLogToFile("errorlog.txt");
		sMaster.m_stopEvent = true;
	
	end method
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Add a command.
	'// -------------------------------------------------------------------------------------------------------------
	method addCommand(name:string, functionPointer:int(args:string))
	
		local c:Command = Command.create(name, functionPointer);
		m_cmdList.addLast(c);
		m_cmdMap.insert(name.toLower(), c);
	
	end method
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Returns true if the console is visible.
	'// -------------------------------------------------------------------------------------------------------------
	method isVisible:byte()
	
		return m_posY > 0;
	
	end method
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Show or hide the console. Returns true if we started showing the console and false if we started hiding it.
	'// -------------------------------------------------------------------------------------------------------------
	method toggle:byte()
	
		if (isVisible()) then
		
			m_step = -20;
			m_input = null;
			return false;
		
		else
		
			m_step = 20;
			m_posY = 1;
			return true;
		
		end if
	
	end method
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Command containers.
	'// -------------------------------------------------------------------------------------------------------------
	field m_cmdList:TList = new TList;
	field m_cmdMap:TMap = new TMap;
	
	'// -------------------------------------------------------------------------------------------------------------
	'// The current console input.
	'// -------------------------------------------------------------------------------------------------------------
	field m_input:string;
	
	'// -------------------------------------------------------------------------------------------------------------
	'// Set this to a positive digit to show the console, and a negative digit to hide the console.
	'// -------------------------------------------------------------------------------------------------------------
	field m_step:int;
	
	'// -------------------------------------------------------------------------------------------------------------
	'// The y-position of the console on the screen.
	'// -------------------------------------------------------------------------------------------------------------
	field m_posY:int;
	
	'// -------------------------------------------------------------------------------------------------------------
	'// The log the console displays.
	'// -------------------------------------------------------------------------------------------------------------
	field m_log:TList = new TList;

end type

global sConsoleManager:ConsoleManager = new ConsoleManager;
